﻿using ExcelParser.Reader;
using ExcelParser.Writer;
using Microsoft.CSharp;
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Reflection;
using System.Text.RegularExpressions;

namespace ExcelParser.CodeGen
{
    public class CodeGenerator<T> where T : CodeWriter, new()
    {
        private string m_configDirectory;
        private string m_outputDll;

        public CodeGenerator(string configDirectory, string outputDll)
        {
            m_configDirectory = configDirectory;
            m_outputDll = outputDll;
        }

        public void GenCode(List<ExcelSheet> sheets)
        {
            for (int i = 0; i < sheets.Count; i++)
            {
                T writer = new T();
                ParseSheet(sheets[i], writer);
            }
        }

        public string GenConfigManager(List<ExcelSheet> sheets, string nspace)
        {
            T writer = new T();
            return writer.GenUnityConfigManager(sheets, nspace);
        }

        /// <summary>
        /// 解析表单
        /// </summary>
        /// <param name="sheet"></param>
        private void ParseSheet(ExcelSheet sheet, CodeWriter writer)
        {
            ConfigParser.Logger?.Invoke(string.Format("解析表单:{0}", sheet.sheet.SheetName));

            Config config = new Config(writer);
            config.Name = sheet.name;

            for (int i = sheet.fieldNameRow.FirstCellNum; i < sheet.fieldNameRow.LastCellNum; i++)
            {
                string type = ExcelReader.GetStringCellValue(sheet.fieldTypeRow.GetCell(i));
                string name = ExcelReader.GetStringCellValue(sheet.fieldNameRow.GetCell(i));
                string desc = ExcelReader.GetStringCellValue(sheet.fieldDescRow.GetCell(i));
                ExcelField field = GenField(type, name, desc);
                if (field == null)
                    continue;

                config.AddField(name, field);
            }

            sheet.config = config;
            sheet.GenCode();
            ConfigParser.Logger?.Invoke(sheet.code);
        }

        /// <summary>
        /// 生成字段
        /// </summary>
        /// <param name="type"></param>
        /// <param name="name"></param>
        /// <param name="desc"></param>
        /// <returns></returns>
        private ExcelField GenField(string type, string name, string desc)
        {
            if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(type))
                return null;

            MatchCollection mc = Regex.Matches(type, @"\w+|[\(\):;]");
            ExcelField rootField = CreateFieldByType(mc[0].Value, name, desc);
            if (rootField == null)
                return null;

            ExcelField field = rootField;

            for (int i = 0; i < mc.Count; i++)
            {
                switch (mc[i].Value)
                {
                    case "(":
                        {
                            ExcelField child = CreateFieldByType(mc[++i].Value, name, desc);
                            field.AddChild(child);
                            field = child;
                            break;
                        }
                    case ")":
                        {
                            field = field.Parent;
                            break;
                        }
                    case ":":
                        {
                            string fieldName = mc[++i].Value;
                            field.Name = fieldName;
                            break;
                        }
                    case ";":
                        {
                            field = field.Parent;
                            ExcelField child = CreateFieldByType(mc[++i].Value, name, desc);
                            field.AddChild(child);
                            field = child;
                            break;
                        }
                }
            }
            return rootField;
        }
        /// <summary>
        /// 根据类型创建字段
        /// </summary>
        /// <param name="type"></param>
        /// <param name="name"></param>
        /// <param name="desc"></param>
        /// <returns></returns>
        private ExcelField CreateFieldByType(string type, string name, string desc)
        {
            ExcelField field;
            switch (type)
            {
                case "array":
                    field = new ArrayField(name, desc);
                    break;
                case "object":
                    field = new ObjectField(name, desc);
                    break;
                default:
                    field = new SimpleField(type, name, desc);
                    break;
            }
            return field;
        }

        /// <summary>
        /// 编译
        /// </summary>
        /// <param name="sheets"></param>
        /// <param name="excelDomain"></param>
        /// <param name="excelAssembly"></param>
        /// <returns></returns>
        public bool Compile(List<ExcelSheet> sheets, List<string> otherCodes, string unityEngineDllPath, out AppDomain excelDomain, out Assembly excelAssembly)
        {
            AppDomainSetup setup = new AppDomainSetup();
            setup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
            excelDomain = AppDomain.CreateDomain("ExcelDomain", null, setup);

            CSharpCodeProvider provider = new CSharpCodeProvider();

            CompilerParameters parameters = new CompilerParameters();
            parameters.ReferencedAssemblies.Add("System.dll");
            parameters.ReferencedAssemblies.Add(unityEngineDllPath);
            parameters.GenerateExecutable = false;
            parameters.OutputAssembly = m_outputDll;
            parameters.IncludeDebugInformation = true;
            //parameters.CompilerOptions = string.Format("/doc:{0}\\ExcelData.xml", m_outputDirectory);

            List<string> codes = new List<string>();
            codes.AddRange(otherCodes);

            for (int i = 0; i < sheets.Count; i++)
            {
                string code = sheets[i].code;
                if (string.IsNullOrEmpty(code))
                    continue;

                codes.Add(code);
            }

            CompilerResults resulte = provider.CompileAssemblyFromSource(parameters, codes.ToArray());
            if (resulte.Errors.HasErrors)
            {
                string strError = "---" + resulte.Errors.Count.ToString() + " Errors---";

                for (int x = 0; x < resulte.Errors.Count; x++)
                {
                    strError = strError + "\nLine:" +
                    resulte.Errors[x].Line.ToString() + " ==>" +
                    resulte.Errors[x].ErrorText;
                }

                ConfigParser.ErrorLogger?.Invoke(strError);
                excelAssembly = null;
                return false;
            }
            excelAssembly = resulte.CompiledAssembly;
            return true;
        }
    }
}